<?PHP
/*
 * $Id: barge-web-smarty.classes.php 139 2009-12-16 15:29:56Z namba0219 $
 * =============================================================================
 * Barge - web application framework on PHP5
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * For more information, see http://barge.googlecode.com/
 */
namespace barge\web\smarty;

use \Smarty;
use \Exception;
use \ArrayAccess;
use \DateTime;

use barge\ClassLoader;
use barge\System;
use barge\io\File;
use barge\io\FileNotFoundException;
use barge\logging\LogFactory;
use barge\utils\ArrayList;
use barge\utils\HashMap;
use barge\utils\String;
use barge\utils\BeanUtils;
use barge\utils\TimeValue;
use barge\web\ActionContext;
use barge\web\Renderer;

/**
 * @version 1.0
 * @author Kenji Namba
 * @since 1.0 2006/08/14
 */
abstract class BaseModel implements ArrayAccess
{
	/**
	 * @var ActionContext
	 */
	protected $context;

	/**
	 * @param ActionContext $context
	 */
	public function __construct(ActionContext $context)
	{
		$this->context = $context;
	}

	/**
	 * @param mixed $index
	 * @param mixed $value
	 */
	public function offsetSet($index, $value)
	{
		$message = "Unsupported operation.";
		throw new IllegalAccessException($message);
	}

	/**
	 * @param mixed $index
	 */
	public function offsetUnset($index)
	{
		$message = "Unsupported operation.";
		throw new IllegalAccessException($message);
	}
}

/**
 * @version 1.0
 * @author Kenji Namba
 * @since 1.0 2006/08/14
 */
class UserModel extends BaseModel
{
	/**
	 * @param mixed $index
	 */
	public function offsetExists($index)
	{
		return ($this->context->getUser()->__get($index) !== null);
	}

	/**
	 * @param mixed $index
	 */
	public function offsetGet($index)
	{
		return ($this->context->getUser()->__get($index));
	}
}

/**
 * @version 1.0
 * @author Kenji Namba
 * @since 1.0 2006/08/14
 */
class RequestModel extends BaseModel
{
	/**
	 * @param mixed $index
	 */
	public function offsetExists($index)
	{
		return ($this->context->getRequest()->getAttribute($index) !== null);
	}

	/**
	 * @param mixed $index
	 */
	public function offsetGet($index)
	{
		return $this->context->getRequest()->getAttribute($index);
	}
}

/**
 * <code>$session</code>セッションモデル
 *
 * @version 1.0
 * @author Kenji Namba
 * @since 1.0 2006/08/14
 */
class SessionModel extends BaseModel
{
	/**
	 * @var Session
	 */
	protected $session;

	/**
	 * @param Context $context
	 */
	public function __construct(ActionContext $context)
	{
		parent::__construct($context);
		$this->session = $context->getRequest()->getSession();
	}

	/**
	 * @param mixed $index
	 * @return bool
	 * @see ArrayAccess#offsetExists
	 */
	public function offsetExists($index)
	{
		return ($this->session->getAttribute($index) !== null);
	}

	/**
	 * @param mixed $index
	 * @return mixed
	 * @see ArrayAccess#offsetGet
	 */
	public function offsetGet($index)
	{
		return $this->session->getAttribute($index);
	}
}

/**
 * <code>$forms</code>モデル
 *
 * @version 1.0
 * @author Kenji Namba
 * @since 1.00 - 2006/08/14
 */
class FormsModel extends BaseModel
{
	/**
	 * @param mixed $index
	 * @see ArrayAccess#offsetExists
	 */
	public function offsetExists($index)
	{
		$form = $this->context->getForm(true, $index, false);
		if ($form == null) {
			$form = $this->context->getForm(false, $index, false);
		}
		return ($form !== null);
	}

	/**
	 * @param mixed $index
	 * @return mixed
	 * @see ArrayAccess#offsetGet
	 */
	public function offsetGet($index)
	{
		$form = $this->context->getForm(true, $index, false);
		if ($form == null) {
			$form = $this->context->getForm(false, $index, false);
		}
		return ($form);
	}
}

/**
 * <code>Renderer</code>のSmarty実装クラス
 *
 * @version 1.0
 * @author Kenji Namba
 * @since 1.0 2006/08/14
 * @see Renderer
 */
class SmartyRenderer extends Renderer
{
	/**
	 * @var Logger
	 */
	private $log;

	/**
	 * @var string
	 */
	private $category = null;

	/**
	 * @var string
	 * @since 1.0 2006/10/19
	 */
	private $buffer = null;

	/**
	 * コンストラクタ
	 *
	 * @param [string path = null] - template name.
	 * @param [string contentType = 'text/html']
	 * @since 1.0 2006/08/14
	 */
    function __construct($templatePath = null, $contentType = 'text/html', $category = null)
    {
    	// Parent constractor.
        parent::__construct($templatePath, $contentType);

        //$this->contentType = $contentType;
    	$this->category = $category;

        // Instance logger.
		$this->log = LogFactory::getLogger();

		// Instance smarty
        $classLoader = System::getClassLoader();
        $this->engine = $classLoader->newInstance("Smarty");
        $handlers = new SmartyHandlers();
        $this->engine->register_modifier('datetime_format', array($handlers, 'datetimeFormatModifier'));
        $this->engine->register_block('rewrite_query', array($handlers, 'rewiteQueryBlock'));
        $this->engine->register_block('html_form', array($handlers, 'htmlFormBlock'));
        $this->engine->register_block('html_a', array($handlers, 'htmlABlock'));
        $this->engine->register_function('html_options_collection', array($handlers, 'htmlOptionsCollectionFunction'));
        $this->engine->register_function('html_text', array($handlers, 'htmlTextFunction'));
        $this->engine->register_function('html_radio', array($handlers, 'htmlRadioFunction'));
        $this->engine->register_function('html_checkbox', array($handlers, 'htmlCheckboxFunction'));
//        $this->engine->register_function('html_password', array($handlers, 'htmlPasswordFunction'));
        $this->engine->register_function('html_textarea', array($handlers, 'htmlTextareaFunction'));
        $this->engine->register_function('html_file', array($handlers, 'htmlFileFunction'));
        $this->engine->register_function('rewrite', array($handlers, 'rewiteFunction'));
    }

	/**
	 * @since 1.0 2006/08/14
	 * @return void
	 */
	public function render()
	{
		$context = $this->context;
		$application = $context->getApplication();

		// ワークディレクトリーを追加
		$templateDir = $context->getWorkDir().'/templates';
		if (!File::exists($templateDir)) {
			File::makeDir($templateDir);
		}

		// プラグインディレクトリーを追加
		$this->getEngine()->plugins_dir[] =
				$application->getRealPath('lib/smarty_ext/');

		// テンプレートファイルが存在するかチェック
		$templateFile = $this->getBaseDir() . $this->getTemplate();
		if (!File::exists($templateFile)) {
			$message = "Smartyテンプレートファイル '{$templateFile}' が存在しません.";
			throw new FileNotFoundException($message);
		}

		// インクルードディレクトリーの追加
 		/* @var engine Smarty */
 		$engine = $this->getEngine();
 		$engine->template_dir = $this->getBaseDir();

 		// Set template complile output direcotry.
 		$engine->compile_dir = $templateDir;

		// Find form instance.
 		$form = $context->getForm(true, null, false);
 		if ($form === null) {
 			$form = $context->getForm(false, null, false);
 		}

 		$baseuri = $context->getApplication()->getBaseURI();

 		$engine->assign('context', $context);
 		$engine->assign('form', $form);
 		$engine->assign('forms', new FormsModel($context));
 		$engine->assign('session', new SessionModel($context));
 		$engine->assign('request', new RequestModel($context));
 		$engine->assign('baseURI', $baseuri === '/' ? '' : $baseuri);
 		$engine->assign('user', new UserModel($context));
 		$engine->assign('messages', ($context->getMessages() != null ?
 				$context->getMessages()->toArray() : Array()));
 		$engine->assign('errors', ($context->getErrors() != null ?
 				$context->getErrors()->toArray() : Array()));

        if (!$this->isBuffering()) {
            $this->result = $engine->fetch($this->getTemplate());
        } else {
        	try {
        		ob_start();
        		// TODO : Content-typeが送信済みかどうかチェック
        		$context->getResponse()->sendHeader("Content-type", $this->contentType);
            	$engine->display($this->getTemplate());
            	ob_end_flush();
        	} catch (Exception $e) {
        		while (@ob_end_clean());
        		throw $e;
        	}
            //flush();
		}
    }

    /**
     * @return bool
     * @since 1.0 2006/08/12
     */
    public function getBuffer()
    {
        return $this->buffer;
    }

    /**
	 * @param string $name
	 * @param mixed $value
     * @since 1.0 2006/08/12
     */
    public function setAttribute($name, $value)
    {
        $this->engine->assign($name, $value);
    }

}

/**
 * Smarty 拡張タグ
 * @author Kenji Namba
 * @version 1.0 2008/09/16
 */
class SmartyHandlers
{
	/**
	 * {html_options_collection}
	 *
	 * @param array $params
	 * @param Smarty $smarty
	 * @return string
	 */
	public function htmlOptionsCollectionFunction($params, Smarty &$smarty)
	{
	    require_once $smarty->_get_plugin_filepath('shared','escape_special_chars');
	    if (!isset($params['options'])) {
	    	$smarty->trigger_error("html_optionsCollection: 属性 'options' は必須です。", E_USER_NOTICE);
	    	return;
	    }

	    $html = '';
	    $options = $params['options'];
	    $selected = isset($params['selected']) ? $params['selected'] : '';
	    $labelProperty = isset($params['labels']) ? $params['labels'] : 'value';
	    $valuesProperty = isset($params['values']) ? $params['values'] : 'key';
	    if ($options instanceof ArrayList) {
	    	foreach ($options as $obj) {
	    		$label = BeanUtils::getProperty($obj, $labelProperty);
	    		$value = BeanUtils::getProperty($obj, $valuesProperty);
	    		$html.='<option value="'
	    			.smarty_function_escape_special_chars($value)
	    			.'" '
	    			.($selected==$value ?'selected':'')
	    			.'>'
	    			.smarty_function_escape_special_chars($label);
	    	}
	    } else if ($options instanceof HashMap) {
		    foreach ($options as $key => $value) {
	    		$html.='<option value="'
	    			.smarty_function_escape_special_chars($key)
	    			.'" '
	    			.($selected==$key ? 'selected' : '')
	    			.'>'
	    			.smarty_function_escape_special_chars($value);
		    }
	    } else {
	    	$smarty->trigger_error("html_optionsCollection: 'options' には HashMap, ArrayListまたはそれらのサブクラスのみ指定できます。", E_USER_NOTICE);
	    	return;
	    }
	    return $html;
	}

	/**
	 * {html_text property='' size='10'}
	 *
	 * @param array $params
	 * @param Smarty $smarty
	 * @return string
	 */
	public function htmlTextFunction($params, Smarty &$smarty)
	{
	    require_once $smarty->_get_plugin_filepath('shared','escape_special_chars');

		$name = null;
		$value = null;
		if (isset($params['property'])) {
			$name = $params['property'];
			$form = $smarty->get_template_vars('form');
			if ($form !== null) {
				$value = smarty_function_escape_special_chars(
					BeanUtils::getProperty($form, $params['property']));
			} else {
//				throw new Exception("html_text : form を取得できません。");
		    	$smarty->trigger_error("html_text : form を取得できません。", E_USER_NOTICE);
		    	return;
			}
		}

	    $html = '<input type="text"';
	    if (!isset($params['value'])) {
	    	$html.= ' value="'.$value.'"';
	    }
		if (!isset($params['name'])) {
	    	$html.= ' name="'.$name.'"';
	    }
	    foreach ($params as $key => $value) {
	    	if ($key !== 'property') {
	    		$html.= ' '.$key.'="'.smarty_function_escape_special_chars($value).'"';
	    	}
	    }
	    $html.= '/>';
	    return $html;
	}


	/**
	 * {html_radio property='type' value='10'}
	 *
	 * @param array $params
	 * @param Smarty $smarty
	 * @return string
	 */
	public function htmlRadioFunction($params, Smarty &$smarty)
	{
	    require_once $smarty->_get_plugin_filepath('shared','escape_special_chars');

		$name = null;
		$value = null;
		if (isset($params['property'])) {
			$name = $params['property'];
			$form = $smarty->get_template_vars('form');
			if ($form !== null) {
				$value = smarty_function_escape_special_chars(
					BeanUtils::getProperty($form, $params['property']));
			} else {
//				throw new Exception("html_text : form を取得できません。");
		    	$smarty->trigger_error("html_text : form を取得できません。", E_USER_NOTICE);
		    	return;
			}
		}

	    $html = '<input type="radio"';
		if (!isset($params['name'])) {
	    	$html.= ' name="'.$name.'"';
	    }
	    if (isset($params['value']) && $value !== null && $value === $params['value']) {
	    	$html.= ' checked="checked"';
	    }

	    foreach ($params as $key => $value) {
	    	if ($key !== 'property') {
	    		$html.= ' '.$key.'="'.smarty_function_escape_special_chars($value).'"';
	    	}
	    }
	    $html.= '/>';
	    return $html;
	}


	/**
	 * {html_text property='' size='10'}
	 *
	 * @param array $params
	 * @param Smarty $smarty
	 * @return string
	 */
	public function htmlCheckboxFunction($params, Smarty &$smarty)
	{
	    require_once $smarty->_get_plugin_filepath('shared','escape_special_chars');
		static $trueExpressions = array('true', 'yes', 'on', 'TRUE', 'YES', 'ON', '1');

		$name = null;
		$formvalue = null;
		if (isset($params['property'])) {
			$name = $params['property'];
			$form = $smarty->get_template_vars('form');
			if ($form !== null) {
				$formvalue = smarty_function_escape_special_chars(
					BeanUtils::getProperty($form, $params['property']));
			} else {
		    	$smarty->trigger_error("html_text : form を取得できません。", E_USER_NOTICE);
		    	return;
			}
		}

	    $html = '<input type="checkbox"';
	    if (!isset($params['value'])) {
	    	$html.= ' value="1"';
	    }
	    if ($formvalue !== null) {
			if ((is_bool($formvalue) && $formvalue) ||
				(isset($params['value']) && strval($formvalue) === $params['value'])) {
		    	$html.= ' checked="checked"';
		    }
		}
		if (!isset($params['name'])) {
	    	$html.= ' name="'.$name.'"';
	    }
	    foreach ($params as $key => $value) {
	    	if ($key !== 'property') {
	    		$html.= ' '.$key.'="'.smarty_function_escape_special_chars($value).'"';
	    	}
	    }
	    $html.= '/>';
	    return $html;
	}

	/**
	 * {html_file property='' size='10'}
	 *
	 * @param array $params
	 * @param Smarty $smarty
	 * @return string
	 */
	public function htmlFileFunction($params, Smarty &$smarty)
	{
	    require_once $smarty->_get_plugin_filepath('shared','escape_special_chars');

		$name = null;
		$value = null;
		if (isset($params['property'])) {
			$name = $params['property'];
		}

	    $html = '<input type="file"';
		if (!isset($params['name'])) {
	    	$html.= ' name="'.$name.'"';
	    }
	    foreach ($params as $key => $value) {
	    	if ($key !== 'property') {
	    		$html.= ' '.$key.'="'.smarty_function_escape_special_chars($value).'"';
	    	}
	    }
	    $html.= '/>';

	    $smarty->assign('_html_form_multipart', 1);
	    return $html;
	}

	/**
	 * {html_textarea property='' size='10'}
	 *
	 * @param array $params
	 * @param Smarty $smarty
	 * @return string
	 */
	public function htmlTextAreaFunction($params, Smarty &$smarty)
	{
	    require_once $smarty->_get_plugin_filepath('shared','escape_special_chars');

		$name = null;
		$value = null;
		if (isset($params['property'])) {
			$name = $params['property'];
			$form = $smarty->get_template_vars('form');
			if ($form !== null) {
				$value = smarty_function_escape_special_chars(
					BeanUtils::getProperty($form, $params['property']));
			} else {
		    	$smarty->trigger_error("html_text : form を取得できません。", E_USER_NOTICE);
		    	return;
			}
		}

	    $html = '<textarea';
		if (!isset($params['name']) && $name !== null) {
	    	$html.= ' name="'.$name.'"';
	    }
	    foreach ($params as $key => $value2) {
	    	if ($key !== 'property') {
	    		$html.= ' '.$key.'="'.smarty_function_escape_special_chars($value2).'"';
	    	}
	    }
	    $html.= '>';
		if (!isset($params['value']) && $value !== null) {
	    	$html.= $value;
	    }
	    $html.= '</textarea>';
	    return $html;
	}

	/**
	 * {$foo|datetime_format:'Y/m/d'}
	 *
	 * @param mixed $object
	 * @param string $format
	 * @return string
	 */
	public function datetimeFormatModifier($object, $format = 'Y/m/d H:i:s')
	{
		if ($object instanceof TimeValue) {
			return $object->format($format);
		} else if (is_numeric($object)) {
			return date($format, intval($object));
		} else if ($object instanceof \DateTime) {
			return $object->format($format);
		} else {
	    	return "";
		}
	}

	/**
	 * <pre>
	 * {html_a action='' module=''}{/html_a}
	 * </pre>
	 * @param array $params
	 * @param $content
	 * @param Smarty $smarty
	 * @return string
	 * @since 1.2.1 2009/03/26
	 */
	public function htmlABlock(array $params, $content, Smarty $smarty)
	{
	    require_once $smarty->_get_plugin_filepath('shared','escape_special_chars');
		if (is_null($content)) {
	        return;
	    }

		/* @var $context ActionContext */
	    $context = $smarty->get_template_vars('context');
	    $moduleId = isset($params['module']) ? $params['module'] : $context->getModuleId();
	    $actionId = isset($params['action']) ? $params['action'] : $context->getActionId();

	    $url = $context->getApplication()->createActionURL($moduleId, $actionId);
	    $token = $context->getTokenId();

	    $html = '<a';
	    $html.= ' action="'.$url.'"';
	    foreach ($params as $key => $value) {
	    	if ($key !== 'action' || $key !== 'module') {
	    		$html.= ' '.$key.'="'.smarty_function_escape_special_chars($value).'"';
	    	}
	    }

	    $html.= '>';
	    // TODO : トークンキー
//	    if ($token !== null) {
//	    	$html.= '<input type="hidden" name="'.ActionContext::TOKEN_KEY.'" value="'.$token.'" />';
//	    }
	    $html.= $content;
	    $html = '</a>';
	    return $html;
	}

	/**
	 * <pre>
	 * {html_form action='' module=''}
	 * {/html_form}
	 * </pre>
	 * @param array $params
	 * @param $content
	 * @param Smarty $smarty
	 * @return string
	 * @since 1.2.1 2009/03/26
	 */
	public function htmlFormBlock(array $params, $content, Smarty $smarty)
	{
	    require_once $smarty->_get_plugin_filepath('shared','escape_special_chars');
		if (is_null($content)) {
	        return;
	    }

		/* @var $context ActionContext */
	    $context = $smarty->get_template_vars('context');
	    $moduleId = isset($params['module']) ? $params['module'] : $context->getModuleId();
	    $actionId = isset($params['action']) ? $params['action'] : $context->getActionId();

	    $url = $context->getApplication()->createActionURL($moduleId, $actionId);
	    $token = $context->getTokenId();

	    $html = '<form';
	    $html.= ' action="'.$url.'"';
	    foreach ($params as $key => $value) {
	    	if ($key !== 'action' || $key !== 'module') {
	    		$html.= ' '.$key.'="'.smarty_function_escape_special_chars($value).'"';
	    	}
	    }
		if (!isset($params['enctype']) && $smarty->get_template_vars('_html_form_multipart') === 1) {
			$html.= ' enctype="multipart/form-data"';
	    }
	    $html.= '>';
	    if ($token !== null) {
	    	$html.= '<input type="hidden" name="'.ActionContext::TOKEN_KEY.'" value="'.$token.'" />';
	    }
	    $html.= $content;
	    $html.= '</form>';
	    $smarty->assign('_html_form_multipart', null);
	    return $html;
	}

	/**
	 * <pre>
	 * {rewrite_query}?id={$form.id}{/rewrite_query}
	 * </pre>
	 * @param array $params
	 * @param $content
	 * @param Smarty $smarty
	 * @return string
	 */
	public function rewiteQueryBlock(array $params, $content, Smarty $smarty)
	{
	    if (is_null($content)) {
	        return;
	    }

		/* @var $context ActionContext */
	    $context = $smarty->get_template_vars('context');
	    $moduleId = isset($params['module']) ? $params['module'] : $context->getModuleId();
	    $actionId = isset($params['action']) ? $params['action'] : $context->getActionId();

	    $url = $context->getApplication()->createActionURL($moduleId, $actionId);
	    if (!empty($content)) {
	    	if ($content[0] === '?') {
	    		$content = substr($content, 1);
	    	}
		    $p = strpos($url, '?');
		    if ($p === false) {
		    	$url .= '?'.$content;
		    } else if ($p === strlen($url)) {
		    	$url .= $content;
		    } else {
		    	$url .= '&'.$content;
		    }
	    }
	    return $url;
	}

	/**
	 * <pre>
	 * {rewrite path='/css/base.css'}
	 * {rewrite action='useradd'}
	 * {rewrite module='user' action='useradd'}
	 * </pre>
	 * @param array $params
	 * @param Smarty $smarty
	 * @return string
	 */
	public function rewiteFunction($params, &$smarty)
	{
		/* @var $context ActionContext */
		$context = $smarty->get_template_vars('context');
		if (isset($params['path'])) {
			$filepath = $params['path'];
			return $context->getApplication()->createURL($filepath);
		} else {
		    $context = $smarty->get_template_vars('context');
		    $moduleId = isset($params['module']) ? $params['module'] : $context->getModuleId();
		    $actionId = isset($params['action']) ? $params['action'] : $context->getActionId();
	//	    return $context->getRequest()->getBaseDir()
	//	    	. String::format($urlformat, Array(module=>$module, action=>$action));
		 	return $context->getApplication()->createActionURL($moduleId, $actionId);
		}

	}

}

